// 18.3 多重继承与虚继承
/**
 * 多重继承（multiple inheritance）是指从多个直接基类（参见15.2.2节，第533页）中产生派生类的能力。多重继承的派生类继承了所有父类的属性。
 *   尽管概念上非常简单，但是多个基类相互交织产生的细节可能会带来错综复杂的设计问题与实现问题。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>
#include <bitset>
#include <regex>
#include <random>
#include <cmath>
#include <iomanip>
#include <cstdio>

int main()
{
    // 18.3.1 多重继承
    /*在派生类的派生列表中可以包含多个基类：[插图]
class Bear : public ZooAnimal { };
class Panda : public Bear, public Endangered { };
      每个基类包含一个可选的访问说明符（参见15.5节，第543页）。一如往常，如果访问说明符被忽略掉了，则关键字class对应的默认访问说明符是private，关键字struct对应的是public（参见15.5节，第546页）。
    */

    // 多重继承的派生类从每个基类中继承状态

    // 派生类构造函数初始化所有基类
    /*构造一个派生类的对象将同时构造并初始化它的所有基类子对象。与从一个基类进行的派生一样（参见15.2.2节，第531页），多重继承的派生类的构造函数初始值也只能初始化它的直接基类：[插图]
// 显示地初始化所有基类
Panda::Panda(std::string name, bool onExhibit) : Bear(name, onExhibit, "Panda"), Endangered(Endangered::critical) { }
// 隐式地使用Bear的默认构造函数初始化Bear子对象
Panda::Panda() : Endangered(Endangered::critical) { }
      派生类的构造函数初始值列表将实参分别传递给每个直接基类。其中基类的构造顺序与派生列表中基类的出现顺序保持一致，而与派生类构造函数初始值列表中基类的顺序无关。
      一个Panda对象按照如下次序进行初始化：
        · ZooAnimal是整个继承体系的最终基类，Bear是Panda的直接基类，ZooAnimal是Bear的基类，所以首先初始化ZooAnimal。
        · 接下来初始化Panda的第一个直接基类Bear。
        · 然后初始化Panda的第二个直接基类Endangered。
        · 最后初始化Panda。
    */

    // 继承的构造函数与多重继承
    /*在C++11新标准中，允许派生类从它的一个或几个基类中继承构造函数（参见15.7.4节，第557页）。但是如果从多个基类中继承了相同的构造函数（即形参列表完全相同），则程序将产生错误：[插图]
struct Base1 {
  Base1() = defalut;
  Base1(const std::string &);
  Base1(std::shared_ptr<int>);
};
struct Base2 {
  Base2() = defalut;
  Base2(const std::string &);
  Base2(int);
};
// 错误：D1试图从两个基类中都继承D1：:D1（conststring&）
struct D1: public Base1, public Base2 {
  using Base1::Base1; // 从Base1继承构造函数
  using Base2::Base2; // 从Base2继承构造函数
};
      如果一个类从它的多个基类中继承了相同的构造函数，则这个类必须为该构造函数定义它自己的版本：[插图]
struct D2: public Base1, public Base2 {
  using Base1::Base1; // 从Base1继承构造函数
  using Base2::Base2; // 从Base2继承构造函数
  // D2必须定义一个接受string的构造函数
  D2(const string &s) : Base1(s), Base2(s) { }
  D2 = defalut;       // 一旦D2定义了它自己的构造函数，则必须出现
};
    */

    // 析构函数与多重继承
    /*和往常一样，派生类的析构函数只负责清除派生类本身分配的资源，派生类的成员及基类都是自动销毁的。合成的析构函数体为空。
        析构函数的调用顺序正好与构造函数相反，在我们的例子中，析构函数的调用顺序是~Panda、~Endangered、~Bear和~ZooAnimal。
    */

    // 多重继承的派生类的拷贝与移动操作
    /*与只有一个基类的继承一样，多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符，则必须在完整的对象上执行拷贝、移动或赋值操作（参见15.7.2节，第553页）。
        只有当派生类使用的是合成版本的拷贝、移动或赋值成员时，才会自动对其基类部分执行这些操作。在合成的拷贝控制成员中，每个基类分别使用自己的对应成员隐式地完成构造、赋值或销毁等工作。
      例如，假设Panda使用了合成版本的成员ling_ling的初始化过程：[插图][插图]
Panda ying_ying("ying_ying");
Panda ling_ling = ying_ying;
      将调用Bear的拷贝构造函数，后者又在执行自己的拷贝任务之前先调用ZooAnimal的拷贝构造函数。一旦ling_ling的Bear部分构造完成，接着就会调用Endangered的拷贝构造函数来创建对象相应的部分。
        最后，执行Panda的拷贝构造函数。合成的移动构造函数的工作机理与之类似。
    */

    // 18.3.2 类型转换与多个基类
    /*在只有一个基类的情况下，派生类的指针或引用能自动转换成一个可访问基类的指针或引用（参见15.2.2节，第530页；参见15.5节，第544页）。
        多个基类的情况与之类似。我们可以令某个可访问基类的指针或引用直接指向一个派生类对象。
      例如，一个ZooAnimal、Bear或Endangered类型的指针或引用可以绑定到Panda对象上：[插图]
// 接受Panda的基类引用的一系列操作
void print(const Bear&);
void highlight(const Endangered&);
ostream& operator<<(ostream&, const ZooAnimal&);
Panda ying_ying("ying_ying");
print(ying_ying);          // 把一个Panda对象传递给一个Bear的引用
highlight(ying_ying);      // 把一个Panda对象传递给一个Endangered的引用
cout << ying_ying << endl; // 把一个Panda对象传递给一个ZooAnimal的引用
      编译器不会在派生类向基类的几种转换中进行比较和选择，因为在它看来转换到任意一种基类都一样好。例如，如果存在如下所示的print重载形式：[插图]
void print(const Bear&);
void print(const Endangered&);
      则通过Panda对象对不带前缀限定符的print函数进行调用将产生编译错误：[插图]
print(ying_ying);          // 二义性错误
    */

    // 基于指针类型或引用类型的查找
    /*与只有一个基类的继承一样，对象、指针和引用的静态类型决定了我们能够使用哪些成员（参见15.6节，第547页）。
        如果我们使用一个ZooAnimal指针，则只有定义在ZooAnimal中的操作是可以使用的，Panda接口中的Bear、Panda和Endangered特有的部分都不可见。
        类似的，一个Bear类型的指针或引用只能访问Bear及ZooAnimal的成员，一个Endangered的指针或引用只能访问Endangered的成员。
表18.1 在ZooAnimal/Endangered中定义的虚函数
函数         含有自定义版本的类
print        ZooAnimal::ZooAnimal
             Bear::Bear
             Endangered::Endangered
             Panda::Panda
highlight    Endangered::Endangered
             Panda::Panda
toes         Bear::Bear
             Panda::Panda
cuddle       Panda::Panda
析构函数      ZooAnimal::ZooAnimal
             Endangered::Endangered
      举个例子，已知我们的类已经定义了表18.1列出的虚函数，考虑下面的这些函数调用：[插图]
Bear *pb = new Panda("ying_ying");
pb->print();     // 正确：Panda::print()
pb->cuddle();    // 错误：不属于Bear的接口
pb->highlight(); // 错误：不属于Bear的接口
delete pb;       // 正确：Panda::~Panda()
      当我们通过Endangered的指针或引用访问一个Panda对象时，Panda接口中Panda特有的部分以及属于Bear的部分都是不可见的：[插图]
Endangered *pe = new Panda("ying_ying");
pe->print();     // 正确：Panda::print()
pe->toes();      // 错误：不属于Endangered的接口
pe->cuddle();    // 错误：不属于Endangered的接口
pe->highlight(); // 正确：Panda::highlight()
delete pe;       // 正确：Panda::~Panda()
    */

    // 18.3.3 多重继承下的类作用域
    /*在只有一个基类的情况下，派生类的作用域嵌套在直接基类和间接基类的作用域中（参见15.6节，第547页）。查找过程沿着继承体系自底向上进行，直到找到所需的名字。派生类的名字将隐藏基类的同名成员。
      在多重继承的情况下，相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到，则对该名字的使用将具有二义性。
      对于一个派生类来说，从它的几个基类中分别继承名字相同的成员是完全合法的，只不过在使用这个名字时必须明确指出它的版本。
      当一个类拥有多个基类时，有可能出现派生类从两个或更多基类中继承了同名成员的情况。此时，不加前缀限定符直接使用该名字将引发二义性。
      要想避免潜在的二义性，最好的办法是在派生类中为该函数定义一个新版本。
    */

    // 18.3.4 虚继承
    /*尽管在派生列表中同一个基类只能出现一次，但实际上派生类可以多次继承同一个类。派生类可以通过它的两个直接基类分别继承同一个间接基类，也可以直接继承某个基类，然后通过另一个基类再一次间接继承该类。
      举个例子，IO标准库的istream和ostream分别继承了一个共同的名为base_ios的抽象基类。该抽象基类负责保存流的缓冲内容并管理流的条件状态。
        iostream是另外一个类，它从istream和ostream直接继承而来，可以同时读写流的内容。
        因为istream和ostream都继承自base_ios，所以iostream继承了base_ios两次，一次是通过istream，另一次是通过ostream。
      在默认情况下，派生类中含有继承链上每个类对应的子部分。如果某个类在派生过程中出现了多次，则派生类中将包含该类的多个子对象。这种默认的情况对某些形如iostream的类显然是行不通的。
        一个iostream对象肯定希望在同一个缓冲区中进行读写操作，也会要求条件状态能同时反映输入和输出操作的情况。假如在iostream对象中真的包含了base_ios的两份拷贝，则上述的共享行为就无法实现了。
      在C++语言中我们通过虚继承（virtual inheritance）的机制解决上述问题。虚继承的目的是令某个类做出声明，承诺愿意共享它的基类。其中，共享的基类子对象称为虚基类（virtual base class）。
        在这种机制下，不论虚基类在继承体系中出现了多少次，在派生类中都只包含唯一一个共享的虚基类子对象。
    */

    // 另一个Panda类
    /*在过去，科学界对于大熊猫属于Raccoon科还是Bear科争论不休。为了如实地反映这种争论，我们可以对Panda类进行修改，令其同时继承Bear和Raccoon。
        此时，为了避免赋予Panda两份ZooAnimal的子对象，我们将Bear和Raccoon继承ZooAnimal的方式定义为虚继承。图18.3描述了新的继承体系。
图18.3 Panda的虚继承层次
ZooAnimal
  |(虚继承)--Bear-----|
  |(虚继承)--Raccoon--|
     Endangered------|--Panda
      虚继承的一个不太直观的特征：必须在虚派生的真实需求出现前就已经完成虚派生的操作。例如在我们的类中，当我们定义Panda时才出现了对虚派生的需求，
        但是如果Bear和Raccoon不是从ZooAnimal虚派生得到的，那么Panda的设计者就显得不太幸运了。
      在实际的编程过程中，位于中间层次的基类将其继承声明为虚继承一般不会带来什么问题。通常情况下，使用虚继承的类层次是由一个人或一个项目组一次性设计完成的。
        对于一个独立开发的类来说，很少需要基类中的某一个是虚基类，况且新基类的开发者也无法改变已存在的类体系。
      虚派生只影响从指定了虚基类的派生类中进一步派生出的类，它不会影响派生类本身。
    */

    // 使用虚基类
    /*我们指定虚基类的方式是在派生列表中添加关键字virtual：[插图]
// 关键字public和virtual的顺序随意
class Racoon : public virtual ZooAnimal { };
class Bear : public virtual ZooAnimal { };
      通过上面的代码我们将ZooAnimal定义为Raccoon和Bear的虚基类。
      virtual说明符表明了一种愿望，即在后续的派生类当中共享虚基类的同一份实例。至于什么样的类能够作为虚基类并没有特殊规定。
      如果某个类指定了虚基类，则该类的派生仍按常规方式进行：[插图]
class Panda : public Bear, public Racoon, public Endangered { };
      Panda通过Raccoon和Bear继承了ZooAnimal，因为Raccoon和Bear继承ZooAnimal的方式都是虚继承，所以在Panda中只有一个ZooAnimal基类部分。
    */

    // 支持向基类的常规类型转换
    /*不论基类是不是虚基类，派生类对象都能被可访问基类的指针或引用操作。例如，下面这些从Panda向基类的类型转换都是合法的：[插图]
void dance(const Bear&);
void rummage(const Raccoon&);
ostream& operator<<(ostream&, const ZooAnimal&);
Panda ying_ying;
dance(ying_ying);   // 正确
rummage(ying_ying); // 正确
cout << ying_ying;  // 正确
    */

    // 虚基类成员的可见性
    /*因为在每个共享的虚基类中只有唯一一个共享的子对象，所以该基类的成员可以被直接访问，并且不会产生二义性。
        此外，如果虚基类的成员只被一条派生路径覆盖，则我们仍然可以直接访问这个被覆盖的成员。但是如果成员被多余一个基类覆盖，则一般情况下派生类必须为该成员自定义一个新的版本。
      例如，假定类B定义了一个名为x的成员，D1和D2都是从B虚继承得到的，D继承了D1和D2，则在D的作用域中，x通过D的两个基类都是可见的。
      如果我们通过D的对象使用x，有三种可能性：
        · 如果在D1和D2中都没有x的定义，则x将被解析为B的成员，此时不存在二义性，一个D的对象只含有x的一个实例。
        · 如果x是B的成员，同时是D1和D2中某一个的成员，则同样没有二义性，派生类的x比共享虚基类B的x优先级更高。
        · 如果在D1和D2中都有x的定义，则直接访问x将产生二义性问题。
      与非虚的多重继承体系一样，解决这种二义性问题最好的方法是在派生类中为成员自定义新的实例。
    */

    // 18.3.5 构造函数与虚继承
    /*在虚派生中，虚基类是由最低层的派生类初始化的。
      当然，继承体系中的每个类都可能在某个时刻成为“最低层的派生类”。只要我们能创建虚基类的派生类对象，该派生类的构造函数就必须初始化它的虚基类。
    */

    // 虚继承的对象的构造方式
    /*含有虚基类的对象的构造顺序与一般的顺序稍有区别：首先使用提供给最低层派生类构造函数的初始值初始化该对象的虚基类子部分，接下来按照直接基类在派生列表中出现的次序依次对其进行初始化。
      例如，当我们创建一个Panda对象时：
        · 首先使用Panda的构造函数初始值列表中提供的初始值构造虚基类ZooAnimal部分。
        · 接下来构造Bear部分。
        · 然后构造Raccoon部分。
        · 然后构造第三个直接基类Endangered。
        · 最后构造Panda部分。
      如果Panda没有显式地初始化ZooAnimal基类，则ZooAnimal的默认构造函数将被调用。如果ZooAnimal没有默认构造函数，则代码将发生错误。
      虚基类总是先于非虚基类构造，与它们在继承体系中的次序和位置无关。
    */

    // 构造函数与析构函数的次序
    /*一个类可以有多个虚基类。此时，这些虚的子对象按照它们在派生列表中出现的顺序从左向右依次构造。
        例如，在下面这个稍显杂乱的TeddyBear派生关系中有两个虚基类：ToyAnimal是直接虚基类，ZooAnimal是Bear的虚基类：[插图]
class Character { };
class BookCharacter : public Character { };
class Toy Animal { };
class TeddyBear : public BookCharacter, public Bear, public virtual Toy Animal { };
      编译器按照直接基类的声明顺序对其依次进行检查，以确定其中是否含有虚基类。如果有，则先构造虚基类，然后按照声明的顺序逐一构造其他非虚基类。
        因此，要想创建一个TeddyBear对象，需要按照如下次序调用这些构造函数：[插图]
ZooAnimal();     // Bear的虚基类
ToyAnimal();     // 直接虚基类
Character();     // 第一个非虚基类的间接基类
BookCharacter(); // 第一个直接非虚基类
Bear();          // 第二个直接非虚基类
TeddyBear();     // 最低层的派生类
      合成的拷贝和移动构造函数按照完全相同的顺序执行，合成的赋值运算符中的成员也按照该顺序赋值。和往常一样，对象的销毁顺序与构造顺序正好相反，首先销毁TeddyBear部分，最后销毁ZooAnimal部分。
    */

    return 0;
}